﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management;
using System.Collections;
using InnovTech.Util.Printer;

namespace InnovTech.Util.WMI.Printer
{
    public static class WMIPrinterUtil
    {
        /// <summary>
        /// Represents the basic query string to select all printers on the local machine.
        /// </summary>
        private static readonly string SelectAllPrintersQuery = "SELECT * FROM Win32_Printer";

        /// <summary>
        /// Builds the query to select the printer for the given printer name.
        /// </summary>
        /// <param name="printerName">The printer name to select</param>
        /// <returns></returns>
        private static string BuildSelectPrinterByNameQuery(string printerName)
        {
            return SelectAllPrintersQuery + " WHERE Name='" + printerName.Replace("\\", "\\\\") + "'";
        }

        /// <summary>
        /// Builds the query to select the default printer on the local machine.
        /// </summary>
        /// <returns></returns>
        private static string BuildSelectDefaultPrinterByNameQuery()
        {
            return SelectAllPrintersQuery + " WHERE Default=True";
        }

        /// <summary>
        /// Creates a <see cref="System.Management.ManagementObjectCollection"/> which contains the managed printer-objects according with the given query string. 
        /// </summary>
        /// <param name="query">the query string to select</param>
        /// <returns></returns>
        private static ManagementObjectCollection GetManagementObjects(string query)
        {
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
            return searcher.Get();
        }

        /// <summary>
        /// Gets a <see cref="ManagementObject"/> which contains the printer infos for the given printer name.
        /// </summary>
        /// <param name="printerName"></param>
        /// <returns></returns>
        private static ManagementObject GetManagedPrinterObjByName(string printerName)
        {
            string query = BuildSelectPrinterByNameQuery(printerName);
            ManagementObjectCollection printers = GetManagementObjects(query);

            if (printers.Count == 0)
            {
                //1. not found
                return null;
            }
            if (printers.Count > 1)
            {
                //2. more than 1 printer which has or contains the given name
                throw new PrinterNameNotUniqueException(printerName);
            }

            //3. found the correct printer
            return new ArrayList(printers)[0] as ManagementObject;
        }

        /// <summary>
        /// Gets all printer-names according with the given query.
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        private static string[] GetPrinters(string query)
        {
            ManagementObjectCollection printers = GetManagementObjects(query);
            return ParsePrinters(printers);
        }

        /// <summary>
        /// Parses all printer-names from the given set of the managed printer objects.
        /// </summary>
        /// <param name="printerObjs"></param>
        /// <returns></returns>
        private static string[] ParsePrinters(ManagementObjectCollection printerObjs)
        {
            List<string> printerNames = new List<string>();
            foreach (var printer in printerObjs)
            {
                printerNames.Add(printer.GetPropertyValue("Name") as string);
            }
            return printerNames.ToArray();
        }

        /// <summary>
        /// Checks whether a printer with the given name is a color printer using the WMI API.
        /// </summary>
        /// <param name="printerName">The name of a printer.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException">Throws an <see cref="ArgumentException"/>, if no printer with the name is found.</exception>
        public static bool CheckColorPrinter(string printerName, bool shouldCheckPrinterValid = false)
        {
            if (shouldCheckPrinterValid && !CheckPrinterValid(printerName))
            {
                return false;
            }

            ManagementObject managedPrinter = GetManagedPrinterObjByName(printerName);
            ArrayList list = new ArrayList((managedPrinter.GetPropertyValue("CapabilityDescriptions") as string[]));
            return list.Contains("Color");
        }

        /// <summary>
        /// Validates the printer by the given name.
        /// </summary>
        /// <param name="printerName"></param>
        /// <exception cref="InnovTech.Util.Printer.PrinterNameNotUniqueException">If more than one printers exist that have or contain the given name.</exception>
        /// <exception cref="InnovTech.Util.Printer.PrinterNotFoundException">If no printer exists which has or contains the given name.</exception>
        public static void ValidatePrinterByName(string printerName)
        {
            ManagementObject managedPrinter = null;

            //1. throws a PrinterNameNotUniqueException if there is more than 1 printers have the given printer name
            managedPrinter = GetManagedPrinterObjByName(printerName);

            if (managedPrinter == null)
            {
                //2. not found
                throw new PrinterNotFoundException(printerName);
            }
            else
            {
                //3. all ok.
                //return true;
            }
        }

        /// <summary>
        /// Checks whether the printer for the given printer name is valid.
        /// </summary>
        /// <param name="printerName">The name of a printer</param>
        /// <returns>True, when and only when there is a printer whose name is with the given name identical.</returns>
        public static bool CheckPrinterValid(string printerName)
        {
            try
            {
                ValidatePrinterByName(printerName);
                return true;
            }
            catch
            {
                return false;
            }

        }

        
        /// <summary>
        /// Gets the default printer name of the current machine.
        /// </summary>
        public static string CurrentDefaultPrinter
        {
            get
            {
                string[] names = GetPrinters(BuildSelectDefaultPrinterByNameQuery());
                if (names == null || names.Length == 0)
                    return null;
                if (names.Length > 1)
                    throw new DefaultPrinterNotUniqueException();
                return names[0];
            }
        }

        /// <summary>
        /// Gets the names of all installed printers of the current machine.
        /// </summary>
        public static string[] InstalledPrinters
        {
            get
            {
                return GetPrinters(SelectAllPrintersQuery);
            }
        }
    }
}
